#!/usr/local/bin/ruby
=begin

Copyright 2010 Arachne Jericho <arachne.jericho@gmail.com>

This file is part of RubyTea.

RubyTea is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

RubyTea is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with RubyTea.  If not, see <http://www.gnu.org/licenses/>.

=end
# 
# Picks teas based on certain parameters. 
#
# Supported parameters: 
#
#   - time : All, Morning, Afternoon, Evening
#

require 'ruby-tea.rb'
require 'cgi'

# What types of tea match to which times of the day
TEA_REGEX = { 
    'Morning' => /black|mate/, 
    'Afternoon' => /green|white/,
    'Evening' => /red|herbal|fruit/,
}

# Number of teas to print out per selection type
NUM_TEAS = {
    :untried => 2,
    :oldest => 2,
    :most_sipped => 2, 
    :least_sipped => 2,
    :random => 15,
}

# Today's date, to make last sipped slightly more friendly
TODAY = Date.today

header('Automatic Tea Picker', 10)
puts <<END
<style type="text/css">
  table { width: 95%; margin-bottom: 2ex; margin-top: 1ex; }
  div.column { float: left; width: 50%; }
  table a { color: #008; }
</style>
END

# Return a flat list of teas whose type match the given regex.
# Each tea information dictionary gets 'Blender' and 'Name' added 
# as keys/values. 
#
# Teas who have a Gone attribute are filtered out.
def get_teas_of_type(type_regex)
    teas = []

    $tea_collection.keys.each do |blender|
        $tea_collection[blender].keys.each do |tea_name|
            tea = $tea_collection[blender][tea_name]

            if (tea.has_key? 'Gone')
                next
            end

            if (tea.has_key? 'Type') && (tea['Type'] =~ type_regex)
                tea['Blender'] = blender
                tea['Name'] = tea_name
                teas << tea
            end
        end
    end

    return teas
end

# Useful for a comparison function to shuffle arrays.
def rand_cmp
    (rand 3) - 1
end

# Picks the first n elements of the given array
def pick_first_n(array, n)
    array[0..(n - 1)]
end

# Does a little variety while picking n elements of the array, 
# without shuffling the erray. We take the first 5n elements, 
# randomize those, and then return n of them.
def pick_first_n_varied(array, n)
    eligible = pick_first_n(array, n * 5)
    pick_first_n(eligible.sort { rand_cmp }, n)
end

# For a flat list of teas, somewhat sorts them by date of last sipping. 
# Many entries will be equal, so we randomize the array first for variety 
# in the semi-sorted result. 
#
# We return NUM_TEAS[:oldest] teas in the result.
def pick_oldest_teas(teas)
    teas_with_last_sipped = teas.reject { |t| !(t.has_key? 'Last Sipped') || t['Last Sipped'].class != Date }

    sorted_teas = (teas_with_last_sipped.sort { rand_cmp }).sort { |t1, t2| 
        t1['Last Sipped'] <=> t2['Last Sipped']
    }

    pick_first_n_varied(sorted_teas, NUM_TEAS[:oldest])
end

# For a flat list of teas, gets the untried ones, and returns NUM_TEAS[:untried]
# of them, randomly chosen.
def pick_untried_teas(teas)
    untried_teas = teas.reject { |t| (t.has_key? 'Last Sipped') && (t['Last Sipped'].class == Date) }
    pick_first_n(untried_teas.sort { rand_cmp }, NUM_TEAS[:untried])
end

# For a flat list of teas, somewhat sorts them by number of sippings, 
# from most to least. 
#
# Sippings have less variety than dates, so not only do we randomize 
# the array before sorting, but we then take the first 2*num elements 
# from the result, randomize those, and then return the first num 
# teas of the secondary result.
def pick_most_sipped_teas(teas)
    sipped_teas = teas.reject { |t| !(t.has_key? 'Sippings') || !(t['Sippings'].kind_of? Integer) }

    sorted_teas = (sipped_teas.sort { rand_cmp }).sort { |t1, t2|
        t2['Sippings'] <=> t1['Sippings']
    }

    pick_first_n_varied(sorted_teas, NUM_TEAS[:most_sipped])
end

# Similar to pick_most_sipped_teas, except we now do this for least sipped teas. 
# This ignores teas without sippings. 
def pick_least_sipped_teas(teas)
    sipped_teas = teas.reject { |t| !(t.has_key? 'Sippings') || !(t['Sippings'].kind_of? Integer) }

    sorted_teas = (sipped_teas.sort { rand_cmp }).sort { |t1, t2|
        t1['Sippings'] <=> t2['Sippings']
    }

    pick_first_n_varied(sorted_teas, NUM_TEAS[:least_sipped])
end

# Picks random NUM_TEAS[:random] teas from the given flat list of teas. 
def pick_random_teas(teas)
    pick_first_n(teas.sort { rand_cmp }, NUM_TEAS[:random])
end

# Pretty prints a list of teas for display, preceeded by the given label
# Note: We sort by blender, then by tea name 
def output_teas(teas, label)
    puts <<-END
    <strong>#{label}</strong><br />
    <table><tr><th>Blender</th><th>Tea</th><th>Type</th><th>Last Sipped</th><th>Sips</th></tr>
    END

    sorted_teas = teas.sort { |t1, t2| 
        r = t1['Blender'] <=> t2['Blender']
        r == 0 ? t1['Name'] <=> t2['Name'] : r
    }

    sorted_teas.each { |tea| 
        if (tea.has_key? 'Last Sipped') && (tea['Last Sipped'].class == Date)
            last_sipped = %Q(<span title="Most recent sip: #{tea['Last Sipped']}">#{TODAY - tea['Last Sipped']} days ago</span>)
        else
            last_sipped = 'untried'
        end
        sippings = tea.has_key?('Sippings') ? tea['Sippings'] : 0

        name_html = tea.has_key?('URL') ? %Q(<a target="tea_view" href="#{CGI::escapeHTML tea['URL']}">#{tea['Name']}</a>) : tea['Name']

        puts <<-END 
        <tr class="#{tea['Type']}">
          <td>#{tea['Blender']}</td>
          <td>#{name_html}</td> 
          <td>#{tea['Type']}</td> 
          <td>#{last_sipped}</td>
          <td>#{sippings} sips</td>
        </tr>
        END
    }
    puts '</table>'
end

# Outputs for a given list of teas, first a selection of the untried/oldest, 
# and second a selection of the most sipped. The 'time' label is printed out 
# as a header.
#
# Two-column display FTW.
def output_tea_selections(teas, time, selections) 
    puts <<-END
    <h2>#{time}</h2>
    <div class="column">
    END

    if selections.include? 'Untried'
        output_teas(pick_untried_teas(teas), 'Untried')
    end
    if selections.include? 'Oldest Sipped'
        output_teas(pick_oldest_teas(teas), 'Oldest Sipped')
    end
    if selections.include? 'Least Sipped'
        output_teas(pick_least_sipped_teas(teas), 'Least Sipped')
    end
    if selections.include? 'Most Sipped'
        output_teas(pick_most_sipped_teas(teas), 'Most Sipped')
    end

    puts <<-END
    </div>
    <div class="column">
    END

    if selections.include? 'Random'
        output_teas(pick_random_teas(teas), 'Random')
    end

    puts '</div><div style="clear: both;"></div>'
end

cgi = CGI.new
time = cgi['time']
selections = cgi.params['selections']

puts <<END
<p>Pick a time?</p>
<form method="GET">
  <select name="time">
END
%w(All Morning Afternoon Evening).each do |t|
    puts %Q(<option value="#{t}" #{t == time ? 'selected' : ''}>#{t}</option>)
end
puts <<END
  </select>
  <br />
END
['Untried', 'Oldest Sipped', 'Least Sipped', 'Most Sipped'].each do |s|
    puts %Q(<input type="checkbox" name="selections" value="#{s}"
    #{((selections.empty?) || (selections.include? s)) ? ' CHECKED' : ''}>&nbsp;#{s})
end
['Random'].each do |s|
    puts %Q(<input type="checkbox" name="selections" value="#{s}"
    #{(selections.include? s) ? ' CHECKED' : ''}>&nbsp;#{s})
end
puts <<END
  <br />
  <input type="submit" />
</form>
</p>
END

def output_teas_for_time(time, selections)
    output_tea_selections(get_teas_of_type(TEA_REGEX[time]), time, selections)
end

if cgi.has_key? 'time'
    case time
    when 'Morning', 'Afternoon', 'Evening'
        output_teas_for_time(time, selections)
    else
        %w(Morning Afternoon Evening).each { |t| output_teas_for_time(t, selections) }
    end
end

footer()
